/***************************************************************** 
	文件名：mem.c 
	abstract :
				把一个给定的虚地址转换为内存的物理地址

	输入参数： 
				pid 接收待查询进程的PID 
				va 接收待查询的虚拟地址 
*****************************************************************/ 
#include  <linux/module.h> 
#include <linux/kernel.h> 
#include <linux/init.h> 
#include <linux/sched.h> 
#include <linux/mm.h> 
#include <asm/pgtable.h> 
#include <asm/page.h> 


/*

Linux中采用了一种通用的四级分页机制.

页全局目录(Page Global Directory)、
页上级目录(Page Upper Directory)、
页中间目录(Page Middle Directory)
页表(Page Table)。

在这种分页机制下，一个完整的线性地址被分为五部分：

页全局目录、页上级目录、页中间目录、页表和偏移量，

但是对于每个部分所占的位数则是不定的，这跟系统所在的体系架构有关.


 PGD、PMD及PT表的表项都占4个字节，因此，把它们定义为无符号长整数，
分别叫做pgd_t、pmd_t及pte_t(pte 即Page table Entry)，在page.h中定义如下：

	typedef struct { unsignedlong pte_low; } pte_t;		页表项
	typedef struct { unsignedlong pmd; } pmd_t;			页上级目录项
	typedef struct { unsigned long pgd; }pgd_t;			页全局目录项
	typedef struct { unsigned long pgprot; } pgprot_t;

    可以看出，Linux没有把这几个类型直接定义长整数而是定义为一个结构，
	这是为了让gcc在编译时进行更严格的类型检查。如果不采用这种方法，
	那么一个无符号长整型数据可以传入任何一个与四种页表相关的函数或宏中，
	这将大大降低程序的健壮性
	
	另外，还定义了几个宏来访问这些结构的成分，
	这也是一种面向对象思想的体现：
	
	#define pte_val(x)      ((x).pte_low)
	#define pmd_val(x)      ((x).pmd)
	#define pgd_val(x)      ((x).pgd)


线性地址

不管系统采用多少级分页模型，线性地址本质上都是索引+偏移量的形式，
甚至你可以将整个线性地址看作N+1个索引的组合，N是系统采用的分页级数。

在四级分页模型下，线性地址被分为5部分，页全局目录、页上级目录、页中间目录、页表和偏移量

在线性地址中，每个页表索引即代表线性地址在对应级别的页表中中关联的页表项。
正是这种索引与页表项的对应关系形成了整个页表映射机制


页表

多个页表项的集合则为页表，一个页表内的所有页表项是连续存放的。页表本质上是一堆数据，
因此也是以页为单位存放在主存中的。因此，在虚拟地址转化物理物理地址的过程中，
每访问一级页表就会访问一次内存。


页表项

从四种页表项的数据结构可以看出，每个页表项其实就是一个无符号长整型数据。
每个页表项分两大类信息：页框基地址和页的属性信息。



*/

MODULE_LICENSE("GPL"); 
static int pid; 
static unsigned long va; 

module_param(pid,int,0644); 
module_param(va,ulong,0644); 

static int find_pgd_init(void) 
{ 
        unsigned long pa = 0; 
        struct task_struct *pcb_tmp = NULL; 
        pgd_t *pgd_tmp = NULL;		// 页全局目录项
        pud_t *pud_tmp = NULL;		// 页中间目录项
        pmd_t *pmd_tmp = NULL;		// 页上级目录项
        pte_t *pte_tmp = NULL;		// 页表项

        printk(KERN_INFO"PAGE_OFFSET = 0x%lx\n",PAGE_OFFSET); 
        printk(KERN_INFO"PGDIR_SHIFT = %d\n",PGDIR_SHIFT); 
        printk(KERN_INFO"PUD_SHIFT = %d\n",PUD_SHIFT); 
        printk(KERN_INFO"PMD_SHIFT = %d\n",PMD_SHIFT); 
        printk(KERN_INFO"PAGE_SHIFT = %d\n",PAGE_SHIFT); 

        printk(KERN_INFO"PTRS_PER_PGD = %d\n",PTRS_PER_PGD); 
        printk(KERN_INFO"PTRS_PER_PUD = %d\n",PTRS_PER_PUD); 
        printk(KERN_INFO"PTRS_PER_PMD = %d\n",PTRS_PER_PMD); 
        printk(KERN_INFO"PTRS_PER_PTE = %d\n",PTRS_PER_PTE); 

        printk(KERN_INFO"PAGE_MASK = 0x%lx\n",PAGE_MASK); 

		/* 核是2.6.24以后的，需要将find_task_by_pid改为find_task_by_vpid */
        //if(!(pcb_tmp = find_task_by_pid(pid))) { 
		if(!(pcb_tmp = find_task_by_vpid(pid))) { 
                printk(KERN_INFO"Can't find the task %d .\n",pid); 
                return 0; 
        } 
        printk(KERN_INFO"pgd = 0x%p\n",pcb_tmp->mm->pgd); 
                /* 判断给出的地址va是否合法(va&lt;vm_end)*/ 
        if(!find_vma(pcb_tmp->mm,va)){ 
                printk(KERN_INFO"virt_addr 0x%lx not available.\n",va); 
                return 0; 
        } 
        pgd_tmp = pgd_offset(pcb_tmp->mm,va); 
        printk(KERN_INFO"pgd_tmp = 0x%p\n",pgd_tmp); 
        printk(KERN_INFO"pgd_val(*pgd_tmp) = 0x%lx\n",pgd_val(*pgd_tmp)); 
        if(pgd_none(*pgd_tmp)){ 
                printk(KERN_INFO"Not mapped in pgd.\n");         
                return 0; 
        } 
        pud_tmp = pud_offset(pgd_tmp,va); 
        printk(KERN_INFO"pud_tmp = 0x%p\n",pud_tmp); 
        printk(KERN_INFO"pud_val(*pud_tmp) = 0x%lx\n",pud_val(*pud_tmp)); 
        if(pud_none(*pud_tmp)){ 
                printk(KERN_INFO"Not mapped in pud.\n"); 
                return 0; 
        } 
        pmd_tmp = pmd_offset(pud_tmp,va); 
        printk(KERN_INFO"pmd_tmp = 0x%p\n",pmd_tmp); 
        printk(KERN_INFO"pmd_val(*pmd_tmp) = 0x%lx\n",pmd_val(*pmd_tmp)); 
        if(pmd_none(*pmd_tmp)){ 
                printk(KERN_INFO"Not mapped in pmd.\n"); 
                return 0; 
        } 
        /*在这里，把原来的pte_offset_map()改成了pte_offset_kernel*/ 
        pte_tmp = pte_offset_kernel(pmd_tmp,va); 

        printk(KERN_INFO"pte_tmp = 0x%p\n",pte_tmp); 
        printk(KERN_INFO"pte_val(*pte_tmp) = 0x%lx\n",pte_val(*pte_tmp)); 
        if(pte_none(*pte_tmp)){ 
                printk(KERN_INFO"Not mapped in pte.\n"); 
                return 0; 
        } 
        if(!pte_present(*pte_tmp)){ 
                printk(KERN_INFO"pte not in RAM.\n"); 
                return 0; 
        } 
        
		pa = (pte_val(*pte_tmp) &tmp  PAGE_MASK) |(va &amp; ~PAGE_MASK); 
        printk(KERN_INFO"virt_addr 0x%lx in RAM is 0x%lx .\n",va,pa); 
        printk(KERN_INFO"contect in 0x%lx is 0x%lx\n",pa, 
                *(unsigned long *)((char *)pa + PAGE_OFFSET)); 
                                                         
        return 0; 

}

static void  find_pgd_exit(void)
{ 
        printk(KERN_INFO"Goodbye!\n"); 

}

module_init(find_pgd_init); 
module_exit(find_pgd_exit);

/*

 测试：打开gedit， 再打开任务管理器，查看gedit的进程号pid=12749,
右键查看其内存映射，找到一个有效的虚拟地址va=0xb8041000，然后：
sudo insmod mem.ko pid=12749 va=0xb8041000

如果你的内核是2.6.24以后的，需要将find_task_by_pid改为find_task_by_vpid

结果：pid=12749 va=0xb8041000

*/
